<?php
// +----------------------------------------------------------------------
// | 授权系统 - 加密服务
// +----------------------------------------------------------------------
declare (strict_types=1);

namespace common\services\system;

use Yii;

/**
 * 加密服务类
 * 提供RSA、AES等加密解密功能
 */
class EncryptService
{
    /**
     * RSA密钥对生成
     * @param int $keySize 密钥大小，默认2048位
     * @return array ['private_key' => string, 'public_key' => string]
     */
    public static function generateRSAKeyPair($keySize = 2048): array
    {
        $config = [
            "private_key_bits" => $keySize,
            "private_key_type" => OPENSSL_KEYTYPE_RSA,
        ];
        
        $res = openssl_pkey_new($config);
        if (!$res) {
            throw new \Exception('RSA密钥对生成失败');
        }
        
        openssl_pkey_export($res, $privateKey);
        $publicKey = openssl_pkey_get_details($res)['key'];
        
        return [
            'private_key' => $privateKey,
            'public_key' => $publicKey,
        ];
    }
    
    /**
     * RSA公钥加密
     * @param string $data 要加密的数据
     * @param string $publicKey 公钥
     * @return string Base64编码的加密数据
     */
    public static function rsaEncrypt(string $data, string $publicKey): string
    {
        $encrypted = '';
        $key = openssl_pkey_get_public($publicKey);
        if (!$key) {
            throw new \Exception('无效的RSA公钥');
        }
        
        // 分块加密（RSA加密有长度限制）
        $maxLength = 117; // 1024位密钥最大加密长度，2048位为245
        $keyDetails = openssl_pkey_get_details($key);
        if ($keyDetails['bits'] == 2048) {
            $maxLength = 245;
        }
        
        $chunks = str_split($data, $maxLength);
        $encrypted = '';
        
        foreach ($chunks as $chunk) {
            $encryptedChunk = '';
            if (!openssl_public_encrypt($chunk, $encryptedChunk, $key)) {
                throw new \Exception('RSA加密失败');
            }
            $encrypted .= $encryptedChunk;
        }
        
        return base64_encode($encrypted);
    }
    
    /**
     * RSA私钥解密
     * @param string $encryptedData Base64编码的加密数据
     * @param string $privateKey 私钥
     * @return string 解密后的数据
     */
    public static function rsaDecrypt(string $encryptedData, string $privateKey): string
    {
        $key = openssl_pkey_get_private($privateKey);
        if (!$key) {
            throw new \Exception('无效的RSA私钥');
        }
        
        $encrypted = base64_decode($encryptedData);
        
        // 分块解密
        $keyDetails = openssl_pkey_get_details($key);
        $chunkSize = $keyDetails['bits'] / 8; // 128 for 1024-bit, 256 for 2048-bit
        
        $chunks = str_split($encrypted, (int)$chunkSize);
        $decrypted = '';
        
        foreach ($chunks as $chunk) {
            $decryptedChunk = '';
            if (!openssl_private_decrypt($chunk, $decryptedChunk, $key)) {
                throw new \Exception('RSA解密失败');
            }
            $decrypted .= $decryptedChunk;
        }
        
        return $decrypted;
    }
    
    /**
     * AES加密
     * @param string $data 要加密的数据
     * @param string $key 密钥（16/24/32字节）
     * @param string $iv 初始向量（16字节）
     * @return string Base64编码的加密数据
     */
    public static function aesEncrypt(string $data, string $key, string $iv = null): string
    {
        $method = 'AES-256-CBC';
        
        // 确保密钥长度正确
        $key = substr(hash('sha256', $key, true), 0, 32);
        
        // 生成或使用提供的IV
        if ($iv === null) {
            $iv = openssl_random_pseudo_bytes(16);
        } else {
            $iv = substr($iv, 0, 16);
        }
        
        $encrypted = openssl_encrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv);
        if ($encrypted === false) {
            throw new \Exception('AES加密失败');
        }
        
        // 将IV和加密数据一起返回
        return base64_encode($iv . $encrypted);
    }
    
    /**
     * AES解密
     * @param string $encryptedData Base64编码的加密数据（包含IV）
     * @param string $key 密钥
     * @return string 解密后的数据
     */
    public static function aesDecrypt(string $encryptedData, string $key): string
    {
        $method = 'AES-256-CBC';
        
        // 确保密钥长度正确
        $key = substr(hash('sha256', $key, true), 0, 32);
        
        $data = base64_decode($encryptedData);
        if ($data === false) {
            throw new \Exception('无效的加密数据');
        }
        
        // 提取IV和加密数据
        $iv = substr($data, 0, 16);
        $encrypted = substr($data, 16);
        
        $decrypted = openssl_decrypt($encrypted, $method, $key, OPENSSL_RAW_DATA, $iv);
        if ($decrypted === false) {
            throw new \Exception('AES解密失败');
        }
        
        return $decrypted;
    }
    
    /**
     * 生成签名
     * @param string $data 要签名的数据
     * @param string $privateKey 私钥
     * @return string Base64编码的签名
     */
    public static function sign(string $data, string $privateKey): string
    {
        $key = openssl_pkey_get_private($privateKey);
        if (!$key) {
            throw new \Exception('无效的RSA私钥');
        }
        
        $signature = '';
        if (!openssl_sign($data, $signature, $key, OPENSSL_ALGO_SHA256)) {
            throw new \Exception('签名失败');
        }
        
        return base64_encode($signature);
    }
    
    /**
     * 验证签名
     * @param string $data 原始数据
     * @param string $signature Base64编码的签名
     * @param string $publicKey 公钥
     * @return bool 签名是否有效
     */
    public static function verify(string $data, string $signature, string $publicKey): bool
    {
        $key = openssl_pkey_get_public($publicKey);
        if (!$key) {
            throw new \Exception('无效的RSA公钥');
        }
        
        $signature = base64_decode($signature);
        $result = openssl_verify($data, $signature, $key, OPENSSL_ALGO_SHA256);
        
        return $result === 1;
    }
    
    /**
     * 生成随机密钥
     * @param int $length 密钥长度
     * @return string
     */
    public static function generateRandomKey(int $length = 32): string
    {
        return bin2hex(openssl_random_pseudo_bytes($length));
    }
    
    /**
     * 计算数据的哈希值
     * @param string $data 数据
     * @param string $algo 算法（md5, sha1, sha256等）
     * @return string 哈希值
     */
    public static function hash(string $data, string $algo = 'sha256'): string
    {
        return hash($algo, $data);
    }
    
    /**
     * HMAC签名
     * @param string $data 数据
     * @param string $key 密钥
     * @param string $algo 算法
     * @return string HMAC值
     */
    public static function hmac(string $data, string $key, string $algo = 'sha256'): string
    {
        return hash_hmac($algo, $data, $key);
    }
}

